package com.xiashitech.agent.service.extload;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Maps;
import com.xiashitech.agent.config.StartConfig;
import com.xiashitech.agent.constants.XSConstants;
import com.xiashitech.agent.db.dao.extload.*;
import com.xiashitech.agent.db.entity.DataWaterMarkConfig;
import com.xiashitech.agent.db.entity.DesensitiseRule;
import com.xiashitech.agent.db.entity.MsgLog;
import com.xiashitech.agent.db.entity.UserPermission;
import com.xiashitech.agent.db.extload.LocalDataSource;
import com.xiashitech.agent.dto.localdata.DesRuleDto;
import com.xiashitech.agent.grpc.XsIncrementClient;
import com.xiashitech.agent.proxy.hutool.HashUtil;
import com.xiashitech.agent.utils.JsonUtil;
import com.xiashitech.agent.utils.XSUtil;
import com.xiashitech.agent.utils.XiashiGrpcUtil;
import com.xiashitech.interfaces.agent.dto.AgentConfigDTO;

import java.sql.SQLException;
import java.util.*;

public class AuthService {

    public void onMessage(String json) {
        XSUtil.debug("Received Push msg from AgentServer：{}", json);
        System.out.println(Thread.currentThread().getId() + " DesenseDebug, AuthService onMessage json: " + json);
        XSUtil.forceLog(json);  // FOR　DEBUG TODO DEL
        String pushId = null; int pushStatus = XSConstants.PushStatus.SUCCESS;
        try {
            JSONObject rootJSO = JSONUtil.parseObj(json);
            pushId = rootJSO.getStr("pushId");
            saveMsgLog(rootJSO);
            String systemId = StartConfig.getParamValueFromKey(StartConfig.systemId);
            if (!StrUtil.equalsIgnoreCase(rootJSO.getStr("systemId"), systemId)){
                throw new IllegalArgumentException("The SystemID check is invalid.");
            }
            if (XSUtil.isEmpty(rootJSO.getStr("pushId")) || XSUtil.isEmpty(rootJSO.getStr("pushKey"))){
                throw new IllegalArgumentException("The pushId and pushKey must not be empty.");
            }
            String pushKey = rootJSO.getStr("pushKey");
            if (XSConstants.PushKey.PERMISSION.equalsIgnoreCase(pushKey)){
                //权限变更
                saveChangedUserPermissions(rootJSO);
            } else if (XSConstants.PushKey.GROUP_CHANGE.equalsIgnoreCase(pushKey)) {
                //组变更（删除或新增用户）
                saveChangedUserPermissionsUserGroupBased(rootJSO);
            } else if (XSConstants.PushKey.DESENSITIZE.equalsIgnoreCase(pushKey)){
                //脱敏变更
                saveChangedDesensitizeRules(rootJSO);
            } else if (XSConstants.PushKey.PERMISSION_INIT.equalsIgnoreCase(pushKey)) {
                //权限初始化/重置
                initUserPermissions(rootJSO);
            } else if (XSConstants.PushKey.DESENSITIZE_INIT.equalsIgnoreCase(pushKey)) {
                //脱敏初始化/重置
                initDesensitizeRules(rootJSO);
            } else {
                throw new IllegalArgumentException("pushKey is invalid");
            }
        }catch (Throwable e){
            pushStatus = XSConstants.PushStatus.FAIL;
            XSUtil.error("[onMessage] ERROR.{}", e.getMessage());
            StartConfig.throwableStackPrinter(e);
        }finally {
            // 给 AgentServer 上报推送结果回执单（服务器收到失败回执或一直没收到回执则定时再重推一次），能解决部分可能的数据不一致场景。
            try {
                // 业务异常不抛给GRPC
                updatePushHistoryStatus(pushId, pushStatus);
            }catch (Throwable throwable){
                XSUtil.error(throwable.getMessage());
            }

        }
    }

    private void initUserPermissions(JSONObject rootJSO){
        // 全量更新权限信息 truncate table 后全量重建
        try {
            List<UserPermission> userPermissionList = extractPermission(rootJSO);
            Integer page = rootJSO.getInt("page");
            Integer pageTotal = rootJSO.getInt("pageTotal");
            if (Objects.isNull(page) || Objects.isNull(pageTotal)){
                throw new IllegalArgumentException("page || pageTotal is invalid");
            }
            if (page == 1){
                // 管理后台手工触发的全量更新和第一次创建本地DB的全量更新
                userPermissionDao.truncate();
            }
            userPermissionDao.saveInitData(userPermissionList);
            userPermissionDao.printAllPermission("initUserPermissions");
            XSUtil.debug("initUserPermissions executed");
        }catch (Throwable throwable){
            XSUtil.error("initUserPermissions ERROR. {}", throwable.getMessage());
            StartConfig.throwableStackPrinter(throwable);
        }
    }

    private void initDesensitizeRules(JSONObject rootJSO){
        // 全量更新脱敏信息 truncate table 后全量重建
        try{
            List<DesRuleDto> ruleDtoList = extractDesensitizeRules(rootJSO);
            // 脱敏不分页，直接删。一次全量推送包含所有脱敏策略。
            desensitiseRuleDao.truncate();
            desensitiseRuleDao.saveInitData(ruleDtoList);
            desensitiseRuleDao.printAllDesensitiseRule("initDesensitizeRules");
            XSUtil.debug("initDesensitizeRules executed");
        }catch (Throwable throwable){
            XSUtil.error("ERROR, initDesensitizeRules. INFO: {}", throwable.getMessage());
            StartConfig.throwableStackPrinter(throwable);
        }
    }

    private void saveChangedUserPermissions(JSONObject rootJSO) {
        try {
            List<UserPermission> userPermissionList = extractPermission(rootJSO);
            userPermissionDao.saveChangeData(userPermissionList);
            userPermissionDao.printAllPermission("saveChangedUserPermissions");
            XSUtil.debug("saveChangedUserPermissions executed");
        } catch (Throwable e) {
            XSUtil.debug(e.getMessage());
            StartConfig.throwableStackPrinter(e);
        }
    }

    private void saveChangedUserPermissionsUserGroupBased(JSONObject rootJSO) {
        try {
            List<UserPermission> userPermissionList = extractPermission(rootJSO);
            userPermissionDao.saveChangeDataUserGroupBased(userPermissionList);
            userPermissionDao.printAllPermission("saveChangedUserPermissionsUserGroupBased");
            XSUtil.debug("saveChangedUserPermissionsUserGroupBased executed");
        } catch (Throwable e) {
            XSUtil.debug(e.getMessage());
            StartConfig.throwableStackPrinter(e);
        }
    }

    private void saveChangedDesensitizeRules(JSONObject rootJSO) {
        try {
            List<DesRuleDto> ruleDtoList = extractDesensitizeRules(rootJSO);
            desensitiseRuleDao.saveChangedData(ruleDtoList);
            desensitiseRuleDao.printAllDesensitiseRule("saveChangedDesensitizeRules");
            XSUtil.debug("saveChangedDesensitizeRules executed.");
        } catch (Throwable e) {
            XSUtil.debug("ERROR saveChangedDesensitizeRules {}", e.getMessage());
            StartConfig.throwableStackPrinter(e);
        }
    }

    private void saveMsgLog(JSONObject rootJson) throws SQLException {
        MsgLog msgLog = MsgLog.builder().gmtCreate(DateUtil.date()).pushKey(rootJson.getStr("pushKey"))
                .pushId(rootJson.getStr("pushId")).systemId(rootJson.getStr("systemId"))
                .serialId(rootJson.getStr("serialId")).build();
        if (Objects.nonNull(rootJson.get("page"))){
            msgLog.setPage(rootJson.getInt("page"));
        }
        if (Objects.nonNull(rootJson.get("pageTotal"))){
            msgLog.setPageTotal(rootJson.getInt("pageTotal"));
        }
        msgLogDao.insert(msgLog);
        XSUtil.debug("success. saveMsgLog.");
    }

    private List<UserPermission> extractPermission(JSONObject rootJSO){
        List<UserPermission> userPermissionList = new ArrayList<>();
        Date now = DateUtil.date();
        String pushId = rootJSO.getStr("pushId");
        JSONArray changeJSA = rootJSO.getJSONArray("change");
        for (int i = 0; i < changeJSA.size(); i++) {
            JSONObject changeItemJSO = changeJSA.get(i, JSONObject.class);
            UserPermission userPermission = UserPermission.builder()
                    .pushId(pushId).gmtCreate(now).gmtModified(now)
                    .instanceName(changeItemJSO.getStr("databaseService"))
                    .databaseName(changeItemJSO.getStr("databaseName"))
                    .schemaName(changeItemJSO.getStr("databaseSchema"))
                    .tabName(changeItemJSO.getStr("tableName"))
                    .userAction(changeItemJSO.getStr("userAction"))
                    .operate(XSUtil.isEmpty(changeItemJSO.getStr("action")) ? "" : changeItemJSO.getStr("action").toUpperCase())
                    .tabAuth(changeItemJSO.getBool("tabAuth"))
                    .tabAuthReason(changeItemJSO.getStr("tabAuthReason"))
                    .colName(changeItemJSO.getStr("columnName"))
                    .colAuth(changeItemJSO.getBool("colAuth"))
                    .colAuthReason(changeItemJSO.getStr("colAuthReason"))
                    .userId(changeItemJSO.getStr("userId"))
                    .ext(changeItemJSO.getStr("ext")).status(changeItemJSO.getStr("status"))
                    .build();
            userPermissionList.add(userPermission);
        }
        return userPermissionList;
    }

    private List<DesRuleDto> extractDesensitizeRules(JSONObject rootJSO){
        List<DesRuleDto> ruleDtoList = new ArrayList<>();
        Date now = DateUtil.date();
        String pushId = rootJSO.getStr("pushId");
        JSONArray changeJSA = rootJSO.getJSONArray("change");
        // table node
        // 脱敏变更业务实际上每次推送都只针对一个表。且包含这个表当前状态下的所有最新脱敏配置信息。
        for (int i = 0; i < changeJSA.size(); i++) {
            List<DesensitiseRule> ruleList = new ArrayList<>();
            JSONObject tableJSO = changeJSA.get(i, JSONObject.class);
            JSONArray authDesenseSetJSA = (JSONArray)JSONUtil.getByPath(tableJSO, "desensitization.authDesenseSet");
            Iterator<Object> columnIterator = authDesenseSetJSA.iterator();
            String schemaName = null, tableName = null;
            // Column
            while (columnIterator.hasNext()) {
                JSONObject columnJSO = (JSONObject)columnIterator.next();
                String entityFQN = columnJSO.getStr("entityFQN");
                String[] names = StrUtil.splitToArray(entityFQN, ".");
                if (names.length != 5){
                    XSUtil.debug("/*/entityFQN JSON node data format is invalid");
                    continue;
                }
                schemaName = names[2];
                tableName = names[3];
                JSONArray authDesenseJSA = columnJSO.getJSONArray("authDesense");
                Iterator<Object> authDesenseIterator = authDesenseJSA.iterator();
                // Column-API
                while (authDesenseIterator.hasNext()) {
                    JSONObject columnApiJsonObject = (JSONObject)authDesenseIterator.next();
                    DesensitiseRule desensitiseRule = DesensitiseRule.builder()
                            .gmtCreate(now).gmtModified(now)
                            .instanceName(names[0]).databaseName(names[1]).schemaName(names[2]).tabName(names[3])
                            .colName(names[4]).isExecute(columnApiJsonObject.getBool("isExecute"))
                            .apiPath(columnApiJsonObject.getStr("apiPath"))
                            .patchPath(columnApiJsonObject.getStr("patchPath"))
                            .regular(columnApiJsonObject.getStr("regular"))
                            .replaceSymbol(columnApiJsonObject.getStr("replaceSymbol"))
                            .ext(columnApiJsonObject.getStr("ext")).pushId(pushId)
                            .build();
                    ruleList.add(desensitiseRule);
                }
            }
            DesRuleDto dto = DesRuleDto.builder().schemaName(schemaName).tableName(tableName)
                    .ruleList(ruleList).build();
            ruleDtoList.add(dto);
        }
        return ruleDtoList;
    }

    public void init(){
        initGrpcIncrementPushChanel();
        initDB();
        applyFullPushWhenStartup();
    }

    private void initDB(){
        commonDao.executeDDL();
    }

    private void initGrpcIncrementPushChanel() {
        String systemId = StartConfig.getParamValueFromKey(StartConfig.systemId);
        if(XiashiGrpcUtil.useGrpc()) {
            //{"systemId": "", "serviceId": "", "serialId": ""}  上游定义的结构备忘
            Map<String, Object> paramMap = new HashMap<>();
            paramMap.put("systemId", systemId);
            // 当前版本 ServiceId 在权限相关逻辑中并无实际意义
            paramMap.put("serviceId", System.getProperty("otel.service.name", systemId));
            paramMap.put("serialId", LocalDataSource.agentInstanceId);
            // 追加一个求serialId Hash的原始字符串（调试过程方便查看相关原始信息）
            paramMap.put("serialIdOrigin", LocalDataSource.agentInstanceIdOrigin);
            XsIncrementClient.getClient().regIncrementCacheObserver(JsonUtil.convertObjectToString(paramMap));
            XSUtil.info("initGrpcIncrementPushChanel done {}", systemId);
            XSUtil.info("###initGrpcIncrementPushChanel paramMap {}", paramMap);

        }
    }

    private void applyFullPushWhenStartup(){
        long cnt = userPermissionDao.count();
        System.out.println(Thread.currentThread().getId() + " DesenseDebug, AuthService applyFullPushWhenStartup cnt: " + cnt);
        userPermissionDao.printAllPermission("applyFullPushWhenStartup");
        desensitiseRuleDao.printAllDesensitiseRule("applyFullPushWhenStartup");
        if (cnt == 0){
            try{
                // 建表成功且表内数据为0 触发一次全量推送
                //String agentService = StartConfig.getParamValueFromKey(StartConfig.agentService);
                String systemId = StartConfig.getParamValueFromKey(StartConfig.systemId);
                Map<String, Object> params = Maps.newHashMap();
                params.put("systemId", systemId);
                params.put("serviceId", System.getProperty("otel.service.name", systemId));
                params.put("serialId", LocalDataSource.agentInstanceId);
                params.put("serialIdOrigin", LocalDataSource.agentInstanceIdOrigin);
                //HttpResponse response =  HttpUtil.createPost(agentService + StartConfig.serverContextPath + "/v1/agent/push/permissionInit")
                HttpResponse response =  HttpUtil.createPost(StartConfig.getAgentServiceRoot() + "/v1/agent/push/permissionInit")
                        .body(JsonUtil.convertObjectToString(params)).timeout(10*1000).execute();
                if (response.isOk()){
                    XSUtil.info("CALL http permissionInit SUCCESS. {}", response.toString());
                }else {
                    XSUtil.info("CALL http permissionInit .  {}", response.toString());
                }
            }catch (Throwable throwable){
                XSUtil.error("CALL http permissionInit FAILURE.{}", throwable.getMessage());
                StartConfig.throwableStackPrinter(throwable);
            }
        }else {
            XSUtil.info("Ignore apply fullPush When Startup (cnt > 0)");
        }
    }

    private void updatePushHistoryStatus(String pushId, int pushStatus){
            String agentService = StartConfig.getParamValueFromKey(StartConfig.agentService);
            Map<Object, Object> params = MapUtil.builder().put("id", pushId).put("status", pushStatus).build();
            XSUtil.info("CALL updatePushHistoryStatus START. REQUEST: {}", params);
            // HttpResponse response =  HttpUtil.createPost(agentService + StartConfig.serverContextPath + "/v1/agent/pushHistory/updatePushHistoryStatus").body(JsonUtil.convertObjectToString(params))
            HttpResponse response =  HttpUtil.createPost(StartConfig.getAgentServiceRoot() + "/v1/agent/pushHistory/updatePushHistoryStatus").body(JsonUtil.convertObjectToString(params))
                    .setConnectionTimeout(3*1000).setReadTimeout(10*1000).execute();
            XSUtil.info("CALL updatePushHistoryStatus RESULT. REQUEST: {}, RESPONSE：{}", params, response.toString());
    }

    /**
     * 获取数据水印配置缓存任务
     */
    public void loadDataWaterMarkConfigCache(DataWaterMarkConfig config) {
        String waterMarkConfigService = AgentConfigDTO.getConfig().getWaterMarkConfigService();
        if (StrUtil.isBlank(waterMarkConfigService)) {
            XSUtil.warn(this.getClass(), "DataWaterMark config service no set.");
            return;
        }
        waterMarkConfigService = XSUtil.makeHttpUrl(waterMarkConfigService);
        HttpResponse response =  HttpUtil.createPost(waterMarkConfigService).body(JsonUtil.convertObjectToString(config))
                .setConnectionTimeout(3*1000).setReadTimeout(10*1000).execute();
        XSUtil.debug(this.getClass(), "load DWM Configs from [{}]. RESPONSE：{}", waterMarkConfigService, response.body());
        if (response.isOk()) {
            JSONObject jsonObject = JSONUtil.parseObj(response.body());
            if (jsonObject.get("data") != null && !jsonObject.getBeanList("data", DataWaterMarkConfig.class).isEmpty()) {
                // 增加水印配置版本号,判断是否要重写数据库
                int versionCode = HashUtil.apHash(response.body());
                if (dwmConfigVersion == 0 ? versionCode != 0 : versionCode != dwmConfigVersion) {
                    AuthService.getInstance().dataWaterMarkConfigDao.saveChangeData(jsonObject.getBeanList("data", DataWaterMarkConfig.class));
                    dwmConfigVersion = versionCode;
                }
            }
        } else {
            XSUtil.debug(this.getClass(), "load DataWaterMark Configs fail. RESPONSE：{}", response);
        }
    }

    private volatile static AuthService instance;
    private final CommonDao commonDao;
    private final UserPermissionDao userPermissionDao;
    private final DesensitiseRuleDao desensitiseRuleDao;
    private final MsgLogDao msgLogDao;
    private final DataWaterMarkConfigDao dataWaterMarkConfigDao;
    private static int dwmConfigVersion = 0;

    private AuthService(CommonDao commonDao, UserPermissionDao userPermissionDao, DesensitiseRuleDao desensitiseRuleDao, MsgLogDao msgLogDao, DataWaterMarkConfigDao dataWaterMarkConfigDao) {
        this.commonDao = commonDao;
        this.userPermissionDao = userPermissionDao;
        this.desensitiseRuleDao = desensitiseRuleDao;
        this.msgLogDao = msgLogDao;
        this.dataWaterMarkConfigDao = dataWaterMarkConfigDao;
    }

    public static AuthService getInstance() {
        if (instance == null) {
            synchronized (AuthService.class) {
                if (instance == null) {
                    instance = new AuthService(new CommonDao(), new UserPermissionDao(), new DesensitiseRuleDao(), new MsgLogDao(), new DataWaterMarkConfigDao());
                }
            }
        }
        return instance;
    }

}
